Chapter 27
MAPI and MFC

by Peter Norton and Rob McGregor

In This Chapter

  The Messaging Application Programming Interface 970
  Two Higher-Level Alternatives: Simple MAPI and CMC 971
  Limited MAPI Functionality in MFC 975
  The CDocument Connection 975
  Sample Program: MAPI1.EXE 981

Microsoft’s Messaging Application Programming Interface (MAPI—also called Mail API) is a messaging architecture that enables applications to interact with multiple messaging systems seamlessly across a variety of hardware platforms. MFC supports a subset of MAPI that allows the transmission of a document through electronic mail to mail-enabled machines. This chapter discusses MAPI, introduces the limited MAPI encapsulation provided by MFC, and presents a basic mail-enabled program.

The Messaging Application Programming Interface

The Microsoft Messaging API is a complex set of functions you can use to mail-enable your applications, giving them the ability to create, edit, transfer, and store mail messages. The MAPI architecture lets applications work with various messaging systems transparently. MAPI provides messaging services in a generic way because it’s an open programming interface. This allows developers to customize functionality and provides for future expansion.

MAPI is built into the Windows 95 and Windows NT 4 operating systems. Because MAPI is an integrated part of Windows, application developers have access to a consistent interface. MAPI is split into two interfaces: one for front-end applications and another for the back-end service providers. This division provides true independence from specific messaging systems. This independence is what makes it possible for applications like the Microsoft Exchange’s in-box to handle both fax messaging and Internet mail services, as well as LAN workgroup messaging. The MAPI programming interfaces provide the features developers need to mail-enable workgroup applications that deal with various messaging systems such as fax, voice mail, and online services like CompuServe and MCI MAIL.

MAPI for Win32 is found in the dynamic link library MAPI32.DLL. MAPI-compliant objects use the Component Object Model (COM) to define their behavior. Also, MAPI is the messaging component of the Microsoft Windows Open Services Architecture (WOSA). WOSA is an open architecture standard that is currently evolving for use with not only messaging, but database, security, and other technologies as well.


Note:  

The Microsoft Windows Open Services Architecture allows you to develop both front-end and back-end software using a standardized interface that fits easily into the distributed computing model.


Client Applications

MAPI client applications come in three general types:

  Applications that are messaging aware. Messaging-aware applications don’t need the full services of the messaging system. They typically include a messaging option such as Send Mail in the File menu as an additional feature.
  Applications that are messaging enabled. Messaging-enabled applications require many of the services of the messaging system. These applications usually run on a network or on an online service.
  Workgroup applications that are messaging based. Messaging-based workgroup applications use the full services of the message system and are designed to execute and interact automatically across a network.

Two Higher-Level Alternatives: Simple MAPI and CMC

A couple of higher-level interface alternatives to the MAPI specification are available to Windows programmers on PC platforms. These abstractions are called Simple MAPI and Common Messaging Calls (CMC). This implementation supports existing messaging-enabled and messaging-aware Windows applications developed in any language that supports DLLs, including C, C++, Delphi, and Visual Basic. Simple MAPI gives you the tools to add basic messaging services to your applications.

When writing messaging-enabled and messaging-aware applications, either Simple MAPI or CMC is an appropriate MAPI interface choice. These interfaces are nearly identical; both provide a set of functions that lets clients create, send, receive, reply to, forward, edit, and delete messages.

Simple MAPI

The Simple MAPI specification uses the MAPI32.DLL library, just as the full MAPI specification does. There are 12 functions in Simple MAPI that give an application the power to send, address, receive, and reply to messages. Table 27.1 describes the functions provided by Simple MAPI.

Table 27.1 The Simple MAPI Function Interface to MAPI

Simple MAPI Function Purpose

MAPIAddress() Addresses a message.
MAPIDeleteMail() Deletes a message.
MAPIDetails() Displays a recipient-details dialog box.
MAPIFindNext() Returns the identifier of the first or next message of a specified type.
MAPIFreeBuffer() Frees memory allocated by the messaging system.
MAPILogoff() Ends a session with the messaging system.
MAPILogon() Establishes a messaging session.
MAPIReadMail() Reads a message.
MAPIResolveName() Displays a dialog box to resolve an ambiguous recipient name.
MAPISaveMail() Saves a message.
MAPISendDocuments() Sends a standard message using a dialog box.
MAPISendMail() Sends a message, allowing greater flexibility in message generation than MAPISendDocuments() does.



Common Messaging Calls

If you think Simple MAPI sounds good, it is—for Windows-only applications written with Visual Basic or some similar development system. But when you are writing new applications with MFC, you shouldn’t use Simple MAPI. Simple MAPI is mainly for backward compatibility with older applications that already use it. If you don’t want the added complexity of the direct MAPI function calls, consider the Common Messaging Calls client interface, a set of 10 functions that provide simple messaging for your client applications. CMC is built on top of the core MAPI subsystem and, unlike Simple MAPI, is independent of the operating system and the hardware used by the underlying messaging system. This makes CMC a good MAPI choice if your application will run on multiple platforms and must provide messaging on each of these platforms. Table 27.2 describes the 10 CMC functions.

Table 27.2 The CMC Functions

Function Description

cmc_send() Sends a message.
cmc_send_documents() Sends a message. This function is string based and is usually used in macro language calls.
cmc_act_on() Performs an action on a specified message.
cmc_list() Lists summary information about messages meeting specified criteria.
cmc_read() Returns a specified message.
cmc_look_up() Looks up addressing information.
cmc_free() Frees memory allocated by the messaging system.
cmc_logoff() Terminates a session with the messaging system.
cmc_logon() Establishes a session with the messaging system.
cmc_query_configuration() Determines information about the installed CMC service.


Note:  

Simple MAPI runs only on Windows-based platforms. CMC is designed to be independent of the operating system and runs on Windows, DOS, and UNIX.


The MapiMessage Structure

MAPI deals with messaging, and the MAPI MapiMessage structure, as defined in MAPI.H, contains information about a MAPI message. This structure is shown here:

typedef struct{
   ULONG            ulReserved;
   LPTSTR           lpszSubject;
   LPTSTR           lpszNoteText;
   LPTSTR           lpszMessageType;
   LPTSTR           lpszDateReceived;
   LPTSTR           lpszConversationID;
   FLAGS            flFlags;
   lpMapiRecipDesc  lpOriginator;
   ULONG            nRecipCount;
   lpMapiRecipDesc  lpRecips;
   ULONG            nFileCount;
   lpMapiFileDesc   lpFiles;
}MapiMessage, FAR *lpMapiMessage;

The members of this structure reveal the type of data a MAPI message uses; the members are described in Table 27.3.

Table 27.3 The Data Members of the MapiMessage Structure

Member Description

ulReserved Reserved; this must be zero.
lpszSubject A pointer to the text string describing the message subject, typically limited to 256 characters or less.
lpszNoteText A pointer to a string containing the message text.
lpszMessageType A pointer to a string indicating the message type. Note that some messaging systems ignore this member.
lpszDateReceived A pointer to a string containing the date that the message was received. The format of this string is YYYY/MM/DD HH:MM, with HH:MM referring to a 24-hour clock.
lpszConversationID A pointer to a string that identifies the conversation thread to which a message belongs.
flFlags A bitmask used to indicate message status flags.
lpOriginator A pointer to a MapiRecipDesc structure containing information about the sender of the message.
nRecipCount The number of MapiRecipDesc structures in the array pointed to by the lpRecips member.
lpRecips A pointer to an array of MapiRecipDesc structures, each containing information about a message recipient.
nFileCount The number of MapiFileDesc structures in the array pointed to by the lpFiles member.
lpFiles A pointer to an array of MapiFileDesc structures, each containing information about a file attachment.

The MapiFileDesc Structure

The MapiMessage structure is supplemented by the MapiFileDesc structure, which contains information about a file attached to a message. This attachment can be either a simple data file or a compound OLE file. The MapiFileDesc structure is shown here:

typedef struct{
   ULONG    ulReserved;
   ULONG    flFlags;
   ULONG    nPosition;
   LPTSTR   lpszPathName;
   LPTSTR   lpszFileName;
   LPVOID   lpFileType;
 }MapiFileDesc, FAR *lpMapiFileDesc;



Examination of the members of this structure shows information needed by MAPI to send a file attachment. These data members are described in Table 27.4.

Table 27.4 The Data Members of the MapiFileDesc Structure

Member Description

ulReserved Reserved; this must be zero.
flFlags A bitmask of attachment flags. These can be MAPI_OLE and MAPI_OLE_STATIC. If no flag is set, the attachment is a standard file.
nPosition An integer value used in the MapiMessage structure member lpszNoteText to indicate where an attachment should be placed in the body of a message.
lpszPathName A pointer to the fully qualified path (including disk drive letter and directory name) of the attachment file.
lpszFileName An optional pointer to the attachment filename as seen by the recipient.
lpFileType An optional pointer to a descriptor that can be used to indicate the type of the attached file.

To determine the type of file being sent, MAPI looks at the file extension. Although this might seem like a crude method of figuring out the file type, it works well in most cases. For example, a file with a .TXT extension means that the file is a text file, an association provided by the registry.

Limited MAPI Functionality in MFC

The full MAPI specification is quite complex and comprehensive. Unfortunately, MFC implements only a small subsection of the entire MAPI. The only functionality implemented is in the form of sending messages and files. Other MAPI features such as retrieval and deletion of messages hasn’t yet been implemented by the MFC developers.

The CDocument Connection

What minimal MAPI support MFC does provide is found buried deep within the CDocument class. In this class, you’ll find the MFC MAPI wrapper method OnFileSendMail() and its corresponding user interface update handler OnUpdateFileSendMail(). The OnFileSendMail() method does a lot to provide messaging services for your MFC applications—with minimal effort on your part!

Doing MAPI the MFC Way

MFC provides a predefined command identifier associated with the OnFileSendMail() and OnUpdateFileSendMail() methods in message map entries. The command identifier ID_FILE_SEND_MAIL (defined in AFXRES.H) enables a Send Mail command in the case of OnUpdateFileSendMail() and sends a document to the user’s mail service provider in the case of OnFileSendMail(). The CDocument class’s message map entries for these methods look like this:

BEGIN_MESSAGE_MAP(CMyDoc, CDocument)
   ON_COMMAND(ID_FILE_SEND_MAIL, OnFileSendMail)
   ON_UPDATE_COMMAND_UI(ID_FILE_SEND_MAIL, OnUpdateFileSendMail)
END_MESSAGE_MAP()

No prototypes or implementations for these methods are required in the derived-document class, although they can be provided if you need them. If the user’s system supports mail services, the methods are available through your ID_FILE_SEND_MAIL menu item. If mail support isn’t available, MFC automatically removes the ID_FILE_SEND_MAIL menu item so that users don’t see it.

The following section looks inside the OnFileSendMail() method to see how it interacts with MAPI. Using these methods as a model, you should be able to write MFC extension DLLs that encapsulate the entire Messaging API within MFC classes.

The CDocument::OnFileSendMail() Method

The OnFileSendMail() method is a real workhorse. There’s a lot going on inside this CDocument MAPI wrapper—Listing 27.1 gives all the gory details. By studying this listing carefully, you can get a feel for how MAPI is really done, under the hood.

Listing 27.1 The CDocument::OnFileSendMail() Method


///////////////////////////////////////////////////////////////////
// CDocument MAPI support

void CDocument::OnFileSendMail()
{
   ASSERT_VALID(this);

   // update handler always gets called first
   ASSERT(_afxIsMailAvail);

   CWaitCursor wait;

   _AFX_MAIL_STATE* pMailState = _afxMailState;
   if (pMailState->m_hInstMail == NULL)
      pMailState->m_hInstMail = ::LoadLibraryA(“MAPI32.DLL”);

   if (pMailState->m_hInstMail == NULL)
   {
      AfxMessageBox(AFX_IDP_FAILED_MAPI_LOAD);
      return;
   }
   ASSERT(pMailState->m_hInstMail != NULL);

   ULONG (PASCAL *lpfnSendMail)(ULONG, ULONG, MapiMessage*,
      FLAGS, ULONG);
   (FARPROC&)lpfnSendMail = GetProcAddress(pMailState->m_hInstMail,
      “MAPISendMail”);

   if (lpfnSendMail == NULL)
   {
      AfxMessageBox(AFX_IDP_INVALID_MAPI_DLL);
      return;
   }
   ASSERT(lpfnSendMail != NULL);

   TCHAR szTempName[_MAX_PATH];
   TCHAR szPath[_MAX_PATH];
   BOOL bRemoveTemp = FALSE;
   if (m_strPathName.IsEmpty() || IsModified())
   {

      // save to temporary path
      VERIFY(GetTempPath(_countof(szPath), szPath) != 0);
      VERIFY(GetTempFileName(szPath, _T(“afx”), 0, szTempName)!= 0);

      // save it, but remember original modified flag
      BOOL bModified = IsModified();
      BOOL bResult = DoSave(szTempName, FALSE);
      SetModifiedFlag(bModified);
      if (!bResult)
      {
        TRACE0(“Warning: file save failed during File.Send Mail.\n”);
        return;
      }
      bRemoveTemp = TRUE;
   }
   else
   {
      // use actual file since it isn’t modified
      lstrcpyn(szTempName, m_strPathName, _countof(szTempName));
   }
#ifdef _UNICODE
   char szTempNameA[_MAX_PATH];
   _wcstombsz(szTempNameA, szTempName, _countof(szTempNameA));
#endif

   // build an appropriate title for the attachment
   TCHAR szTitle[_MAX_PATH];
   if (!m_strPathName.IsEmpty())
      AfxGetFileName(m_strPathName, szTitle, _countof(szTitle));
   else
   {
      lstrcpyn(szTitle, m_strTitle, _countof(szTitle));
      if (m_strTitle.Find(‘.’) == -1) // no extension
      {
         // append the default suffix if there is one
         CString strExt;
         CDocTemplate* pTemplate = GetDocTemplate();
         if (pTemplate != NULL &&
            pTemplate->GetDocString(strExt, CDocTemplate::filterExt))
         {
            lstrcat(szTitle, strExt);
         }
      }
   }

#ifdef _UNICODE
   char szTitleA[_MAX_PATH];
   _wcstombsz(szTitleA, szTitle, _countof(szTitleA));
#endif

   // prepare the file description (for the attachment)
   MapiFileDesc fileDesc;
   memset(&fileDesc, 0, sizeof(fileDesc));
   fileDesc.nPosition = (ULONG)-1;
#ifdef _UNICODE
   fileDesc.lpszPathName = szTempNameA;
   fileDesc.lpszFileName = szTitleA;
#else
   fileDesc.lpszPathName = szTempName;
   fileDesc.lpszFileName = szTitle;
#endif

   // prepare the message (empty with 1 attachment)
   MapiMessage message;
   memset(&message, 0, sizeof(message));
   message.nFileCount = 1;
   message.lpFiles = &fileDesc;

   // prepare for modal dialog box
   AfxGetApp()->EnableModeless(FALSE);
   HWND hWndTop;
   CWnd* pParentWnd = CWnd::GetSafeOwner(NULL, &hWndTop);
   // some extra precautions are required to use MAPISendMail as it
   // tends to enable the parent window in between dialogs (after
   // the login dialog, but before the send note dialog).
   pParentWnd->SetCapture();
   ::SetFocus(NULL);
   pParentWnd->m_nFlags |= WF_STAYDISABLED;

   int nError = lpfnSendMail(0, (ULONG)pParentWnd->GetSafeHwnd(),
      &message, MAPI_LOGON_UI|MAPI_DIALOG, 0);

   // after returning from the MAPISendMail call, the window must
   // be re-enabled and focus returned to the frame to undo the
   // workaround done before the MAPI call.
   ::ReleaseCapture();
   pParentWnd->m_nFlags &= ~WF_STAYDISABLED;

   pParentWnd->EnableWindow(TRUE);
   ::SetActiveWindow(NULL);
   pParentWnd->SetActiveWindow();
   pParentWnd->SetFocus();
   if (hWndTop != NULL)
      ::EnableWindow(hWndTop, TRUE);
   AfxGetApp()->EnableModeless(TRUE);

   if (nError != SUCCESS_SUCCESS &&
      nError != MAPI_USER_ABORT && nError != MAPI_E_LOGIN_FAILURE)
   {
      AfxMessageBox(AFX_IDP_FAILED_MAPI_SEND);
   }

   // remove temporary file, if temporary file was used
   if (bRemoveTemp)
      CFile::Remove(szTempName);
}



The OnFileSendMail() method is quite long and complex. Let’s see if I can shed a little light on its internal workings.

The first thing the method does is check to see whether mail services are available on the user’s machine. This is done by attempting to get an instance handle to the MAPI32.DLL:

pMailState->m_hInstMail = ::LoadLibraryA(“MAPI32.DLL”);

If this LoadLibrary() call fails, MAPI isn’t available and the method returns. If the call to LoadLibrary() returns an instance handle, the OnFileSendMail() method checks to see whether this MAPI32.DLL contains the MAPISendMail() function. It does this by attempting to get a pointer to the MAPISendMail() function using the Win32 API function GetProcAddress(), like this:

ULONG (PASCAL *lpfnSendMail)(ULONG, ULONG, MapiMessage*, FLAGS,
   ULONG);
(FARPROC&)lpfnSendMail = GetProcAddress(pMailState->m_hInstMail,
   “MAPISendMail”);

If the lpfnSendMail function pointer is NULL after this call to GetProcAddress(), the MAPISendMail() function doesn’t exist in the DLL, and there’s no point in continuing. If the pointer is not NULL, however, the method moves forward and checks to see whether the current document has been saved or modified since it was last saved. Regardless of whether or not it has been modified since the last save, the document is saved to a temporary file. The path and filename for this temporary file are determined by calling the GetTempPath() and GetTempFileName() Win32 API functions, like this:

VERIFY(GetTempPath(_countof(szPath), szPath) != 0);
VERIFY(GetTempFileName(szPath, _T(“afx”), 0, szTempName)!= 0);

These statements store the path in the variable szPath and the filename in the variable szTempName. (Note that the “afx” string used in GetTempFileName() is a three-letter prefix for the new temporary file.)

The next order of business is to build an appropriate title for the file attachment. This is done by calling the undocumented MFC global function AfxGetFileName() if a temporary filename was generated; if the document was previously saved but not modified, you call the CDocTemplate::GetDocString() method. In either case, the attachment title is stored in the variable szTitle.

Next, a MAPI file description is prepared using the Simple MAPI MapiFileDesc structure (discussed in the section “The MapiFileDesc Structure,” earlier in this chapter), and the szTempName and szTitle variables are used to fill in the MapiFileDesc lpszPathName and lpszFileName members:

MapiFileDesc fileDesc;
   memset(&fileDesc, 0, sizeof(fileDesc));
   fileDesc.nPosition = (ULONG)-1;
#ifdef _UNICODE
   fileDesc.lpszPathName = szTempNameA;
   fileDesc.lpszFileName = szTitleA;
#else
   fileDesc.lpszPathName = szTempName;
   fileDesc.lpszFileName = szTitle;
#endif

A MapiMessage structure is then prepared, with the message being empty except for the file attachment, like this:

// prepare the message (empty with 1 attachment)
MapiMessage message;
memset(&message, 0, sizeof(message));
message.nFileCount = 1;
message.lpFiles = &fileDesc;

OnFileSendMail() then takes some extra precautions to keep the parent window from getting the input focus until the mailing process is done, telling the parent window to stay disabled until further notice, like this:

pParentWnd->SetCapture();
::SetFocus(NULL);
pParentWnd->m_nFlags |= WF_STAYDISABLED;

At last, the mail message is sent, using the function pointer retrieved earlier to call the MAPISendMail() function:

int nError = lpfnSendMail(0, (ULONG)pParentWnd->GetSafeHwnd(),
   &message, MAPI_LOGON_UI|MAPI_DIALOG, 0);

As the final step, the parent window is reenabled:

::ReleaseCapture();
pParentWnd->m_nFlags &= ~WF_STAYDISABLED;
pParentWnd->EnableWindow(TRUE);
::SetActiveWindow(NULL);
pParentWnd->SetActiveWindow();
pParentWnd->SetFocus();

And that’s all there is to it! What? You thought it was easier? Let’s see how all this code, wrapped nicely into the CDocument::OnFileSendMail() function gets the job done in a sample application.

Sample Program: MAPI1.EXE

The sample program MAPI1 shows how easy it is to mail-enable your applications with the MFC MAPI wrapper methods in CDocument. Because the only MFC support is in CDocument, a document/view application model is used. This is the simplest way to go about adding the Send Mail command to your applications. The Visual C++ AppWizard allows you to choose the MAPI option when creating a skeleton application, as shown in Figure 27.1.


Figure 27.1  The Visual C++ AppWizard lets you add basic MAPI Send Mail functionality quickly and painlessly.

The sample program MAPI1 uses a CEditView-derived view class. To enable MAPI, the only steps you need to perform are as follows:

1.  Add a Send Mail menu item and give it the corresponding command identifier ID_FILE_SEND_MAIL.
2.  Add the two CDocument message map entries ON_COMMAND() and ON_UPDATE_COMMAND_UI() (both referring to the ID_FILE_SEND_MAIL identifier, of course) to provide the functionality directly from the CDocument class.

After these steps are complete and the program is built (assuming that mail is enabled on the system), you can open a document and choose the File, Send Mail menu command to see MAPI at work. When you activate the OnFileSendMail() method, it starts the mail service (see Figure 27.2).


Note:  

The document opened in the application for this example is MAPI1.REG, the AppWizard-generated file used by REGEDIT.


If you are sending an Internet mail message, the service must be properly configured using the Windows 95 Internet Mail information dialog box, shown in Figure 27.3.


Figure 27.2  The Choose Profile dialog box is the gateway to the Microsoft Exchange.


Figure 27.3  Setting up Internet mail information.

After the mail connection is ready, you must select an address book (see Figure 27.4) that contains information about where to send the mail message (and any attachments).


Figure 27.4  Selecting an address book.

The final stop for the message before it’s routed to the intended receiver is the mail service’s out-box, where the message is made ready for delivery (see Figure 27.5).


Figure 27.5  At last, the MAPI1.REG message appears in Exchange, ready for delivery.

Summary

The Messaging Application Programming Interface allows applications to create, send, receive, edit, forward, and delete messages across a network. Two function-based interfaces to MAPI functionality are commonly used by Windows programmers: Simple MAPI (which provides a limited subset of MAPI) and Common Messaging Calls (which provides a similar interface).

The MFC library provides the CDocument::OnFileSendMail() method to handle the details of preparing a document as a mail attachment and sending it to the mail service. The use of this advanced feature is as simple as including the command identifier ID_FILE_SEND_MAIL and specifying the command in the document’s message map.